﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace SR.ExpressionEvaluator.Lib
{
    public partial class ExpressionTextBox : UserControl, INotifyPropertyChanged
    {
        #region Private fields

        private string _Text;
        private ExpressionTypes lastExpressionType;

        #endregion

        public event PropertyChangedEventHandler PropertyChanged;
        private System.Windows.Visibility _IntellisenseVisible;
        private System.Windows.Media.FontFamily _Font;
        private bool _IsMultiLine;

        public string Text
        {
            get
            {
                return _Text;
            }
            set
            {
                _Text = value;
                highlightText();
                notifyPropertyChanged("Text");
            }
        }

        public Visibility IntellisenseVisible
        {
            get
            {
                return _IntellisenseVisible;
            }
            set
            {
                _IntellisenseVisible = value;
                notifyPropertyChanged("IntellisenseVisible");
            }
        }

        public FontFamily Font
        {
            get
            {
                return _Font;
            }
            set
            {
                _Font = value;
                notifyPropertyChanged("Font");
            }
        }

        public bool IsMultiLine
        {
            get
            {
                return _IsMultiLine;
            }
            set
            {
                _IsMultiLine = value;
                notifyPropertyChanged("IsMultiLine");
            }
        }

        public ExpressionTextBox()
        {
            this.IntellisenseVisible = System.Windows.Visibility.Visible;
            this.Font = new FontFamily("Consolas");
            this.FontSize = 12;
            this.IsMultiLine = false;

            InitializeComponent();
            
            this.DataContext = this;

            this.txtEditorSource.TextChanged += new TextChangedEventHandler(txtEditorSource_TextChanged);
            this.txtEditorSource.KeyDown += new KeyEventHandler(txtEditorSource_KeyDown);
            this.lstProbables.MouseLeftButtonUp += new MouseButtonEventHandler(lstProbables_MouseLeftButtonUp);
            this.txtEditorView.MouseLeftButtonUp += new MouseButtonEventHandler(txtEditorView_MouseLeftButtonUp);
            this.lstProbables.KeyUp += new KeyEventHandler(lstProbables_KeyUp);
        }

        #region Private methods

        void txtEditorSource_TextChanged(object sender, TextChangedEventArgs e)
        {
            this.highlightText();
        }

        void txtEditorView_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            this.txtEditorSource.Focus();
        }

        void txtEditorSource_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Space)
            {
                bool inString = false;
                for (int i = 0; i < txtEditorSource.Text.Length; i++)
                {
                    if (i == 0 && txtEditorSource.Text[i] == '\"')
                    {
                        inString = !inString;
                    }
                    if (txtEditorSource.Text[i] == '\"' && txtEditorSource.Text[i - 1] != '\\')
                    {
                        inString = !inString;
                    }
                }
                if (!inString)
                {
                    e.Handled = true;
                }
            }
            else if (e.Key == Key.Enter && this.lstProbables.ItemsSource != null)
            {
                selectIntelliText();
            }
            else if (e.Key == Key.Down && this.lstProbables.ItemsSource != null)
            {
                this.lstProbables.Focus();
                if (this.lstProbables.SelectedItem == null)
                {
                    foreach (KeyValuePair<string, string> item in this.lstProbables.ItemsSource as Dictionary<string, string>)
                    {
                        this.lstProbables.SelectedItem = item;
                        break;
                    }
                }
            }
        }

        void lstProbables_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (this.lstProbables.SelectedItem != null)
            {
                selectIntelliText();
            }
        }

        void lstProbables_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                this.txtEditorSource.Focus();
                selectIntelliText();
            }
            if (e.Key == Key.Escape)
            {
                this.txtEditorSource.Focus();
            }
        }

        private void selectIntelliText()
        {
            if (this.lstProbables.SelectedItem == null)
            {
                foreach (KeyValuePair<string, string> item in this.lstProbables.ItemsSource as Dictionary<string, string>)
                {
                    this.lstProbables.SelectedItem = item;
                    break;
                }
            }

            object selectedItem = lstProbables.SelectedItem;
            if (selectedItem != null)
            {
                this.txtEditorSource.Text = this.txtEditorSource.Text.Substring(0, this.txtEditorSource.Text.LastIndexOf(((Run)this.txtEditorView.Inlines.Last()).Text));
                this.txtEditorSource.Text += ((KeyValuePair<string, string>)selectedItem).Key;

                highlightText();
            }

            this.lstProbables.ItemsSource = null;
            this.txtEditorSource.SelectionStart = this.txtEditorSource.Text.Length;
        }

        private void highlightText()
        {
            List<Inline> inlines = new List<Inline>();

            IEvaluator evaluator = Factory.GetEvaluator();
            evaluator.ExpressionPartFound += ((epart, type) =>
            {
                lastExpressionType = type;

                switch (type)
                {
                    case ExpressionTypes.Literal:
                        inlines.Add(new Run()
                        {
                            Text = epart,
                            Foreground = new SolidColorBrush(Colors.Black)
                        });
                        break;

                    case ExpressionTypes.String:
                        inlines.Add(new Run()
                        {
                            Text = epart,
                            Foreground = new SolidColorBrush(Colors.Brown)
                        });
                        break;

                    case ExpressionTypes.Operand:
                        inlines.Add(new Run()
                        {
                            Text = epart,
                            Foreground = new SolidColorBrush(Colors.Gray)
                        });
                        break;

                    case ExpressionTypes.Function:
                        inlines.Add(new Run()
                        {
                            Text = epart,
                            Foreground = new SolidColorBrush(Colors.Blue)
                        });
                        break;

                    case ExpressionTypes.Variable:
                        inlines.Add(new Run()
                        {
                            Text = epart,
                            Foreground = new SolidColorBrush(Colors.Orange)
                        });
                        break;

                    case ExpressionTypes.Boolean:
                        inlines.Add(new Run()
                        {
                            Text = epart,
                            Foreground = new SolidColorBrush(Colors.Green)
                        });
                        break;

                    default:
                    case ExpressionTypes.Error:
                        inlines.Add(new Run()
                        {
                            Text = epart,
                            Foreground = new SolidColorBrush(Colors.Red),
                            TextDecorations = TextDecorations.Underline
                        });
                        break;
                }
            });
            evaluator.Validate(this.txtEditorSource.Text);

            this.txtEditorView.Inlines.Clear();
            foreach (Inline inline in inlines)
            {
                this.txtEditorView.Inlines.Add(inline);
            }

            if (this.txtEditorView.Inlines.Count > 0)
            {
                Dictionary<string, string> probables = evaluator.GetProbables(((Run)this.txtEditorView.Inlines.Last()).Text);
                lstProbables.ItemsSource = probables;
            }
        }

        private void notifyPropertyChanged(string property)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }

        #endregion
    }
}
